package pv.fbv3;

import java.util.Collections;
import java.util.List;

import pv.finder.R;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.View;

import com.facebook.FacebookException;
import com.facebook.Request;
import com.facebook.Response;
import com.facebook.Session;
import com.facebook.SessionDefaultAudience;
import com.facebook.SessionLoginBehavior;
import com.facebook.internal.SessionAuthorizationType;
import com.facebook.internal.Utility;
import com.facebook.model.GraphUser;

@SuppressWarnings("deprecation")
public class LoginHelperv2 {
	private Session session;
	private GraphUser user = null;
	private Session userInfoSession = null;
	private boolean fetchUserInfo;
	private UserInfoChangedCallback userInfoChangedCallback;
	private Fragment parentFragment;
	private LoginHelperv2Properties properties = new LoginHelperv2Properties();
	protected Context context;
	private static final String TAG = "login_helper";

	static class LoginHelperv2Properties {
		private SessionDefaultAudience defaultAudience = SessionDefaultAudience.FRIENDS;
		private List<String> permissions = Collections.<String> emptyList();
		private SessionAuthorizationType authorizationType = null;
		private OnErrorListener onErrorListener;
		private SessionLoginBehavior loginBehavior = SessionLoginBehavior.SSO_WITH_FALLBACK;
		private Session.StatusCallback sessionStatusCallback;

		public void setOnErrorListener(OnErrorListener onErrorListener) {
			this.onErrorListener = onErrorListener;
		}

		public OnErrorListener getOnErrorListener() {
			return onErrorListener;
		}

		public void setDefaultAudience(SessionDefaultAudience defaultAudience) {
			this.defaultAudience = defaultAudience;
		}

		public SessionDefaultAudience getDefaultAudience() {
			return defaultAudience;
		}

		public void setReadPermissions(List<String> permissions, Session session) {
			if (SessionAuthorizationType.PUBLISH.equals(authorizationType)) {
				throw new UnsupportedOperationException(
						"Cannot call setReadPermissions after setPublishPermissions has been called.");
			}
			if (validatePermissions(permissions, SessionAuthorizationType.READ,
					session)) {
				this.permissions = permissions;
				authorizationType = SessionAuthorizationType.READ;
			}
		}

		public void setPublishPermissions(List<String> permissions,
				Session session) {
			if (SessionAuthorizationType.READ.equals(authorizationType)) {
				throw new UnsupportedOperationException(
						"Cannot call setPublishPermissions after setReadPermissions has been called.");
			}
			if (validatePermissions(permissions,
					SessionAuthorizationType.PUBLISH, session)) {
				this.permissions = permissions;
				authorizationType = SessionAuthorizationType.PUBLISH;
			}
		}

		private boolean validatePermissions(List<String> permissions,
				SessionAuthorizationType authType, Session currentSession) {
			if (SessionAuthorizationType.PUBLISH.equals(authType)) {
				if (Utility.isNullOrEmpty(permissions)) {
					throw new IllegalArgumentException(
							"Permissions for publish actions cannot be null or empty.");
				}
			}
			if (currentSession != null && currentSession.isOpened()) {
				if (!Utility.isSubset(permissions,
						currentSession.getPermissions())) {
					Log.e(TAG,
							"Cannot set additional permissions when session is already open.");
					return false;
				}
			}
			return true;
		}

		List<String> getPermissions() {
			return permissions;
		}

		public void clearPermissions() {
			permissions = null;
			authorizationType = null;
		}

		public void setLoginBehavior(SessionLoginBehavior loginBehavior) {
			this.loginBehavior = loginBehavior;
		}

		public SessionLoginBehavior getLoginBehavior() {
			return loginBehavior;
		}

		public void setSessionStatusCallback(Session.StatusCallback callback) {
			this.sessionStatusCallback = callback;
		}

		public Session.StatusCallback getSessionStatusCallback() {
			return sessionStatusCallback;
		}
	}

	/**
	 * Specifies a callback interface that will be called when the button's
	 * notion of the current user changes (if the fetch_user_info attribute is
	 * true for this control).
	 */
	public interface UserInfoChangedCallback {
		/**
		 * Called when the current user changes.
		 * 
		 * @param user
		 *            the current user, or null if there is no user
		 */
		void onUserInfoFetched(GraphUser user);
	}

	/**
	 * Callback interface that will be called when a network or other error is
	 * encountered while logging in.
	 */
	public interface OnErrorListener {
		/**
		 * Called when a network or other error is encountered.
		 * 
		 * @param error
		 *            a FacebookException representing the error that was
		 *            encountered.
		 */
		void onError(FacebookException error);
	}

	/**
	 * Create the LoginHelperv2.
	 * 
	 * @see View#View(Context)
	 */
	public LoginHelperv2(Context context) {
		this.context = context;
		initializeActiveSessionWithCachedToken(context);
		finishInit();
	}

	/**
	 * Sets an OnErrorListener for this instance of LoginHelperv2 to call into
	 * when certain exceptions occur.
	 * 
	 * @param onErrorListener
	 *            The listener object to set
	 */
	public void setOnErrorListener(OnErrorListener onErrorListener) {
		properties.setOnErrorListener(onErrorListener);
	}

	/**
	 * Returns the current OnErrorListener for this instance of LoginHelperv2.
	 * 
	 * @return The OnErrorListener
	 */
	public OnErrorListener getOnErrorListener() {
		return properties.getOnErrorListener();
	}

	/**
	 * Sets the default audience to use when the session is opened. This value
	 * is only useful when specifying write permissions for the native login
	 * dialog.
	 * 
	 * @param defaultAudience
	 *            the default audience value to use
	 */
	public void setDefaultAudience(SessionDefaultAudience defaultAudience) {
		properties.setDefaultAudience(defaultAudience);
	}

	/**
	 * Gets the default audience to use when the session is opened. This value
	 * is only useful when specifying write permissions for the native login
	 * dialog.
	 * 
	 * @return the default audience value to use
	 */
	public SessionDefaultAudience getDefaultAudience() {
		return properties.getDefaultAudience();
	}

	/**
	 * Set the permissions to use when the session is opened. The permissions
	 * here can only be read permissions. If any publish permissions are
	 * included, the login attempt by the user will fail. The LoginHelperv2 can
	 * only be associated with either read permissions or publish permissions,
	 * but not both. Calling both setReadPermissions and setPublishPermissions
	 * on the same instance of LoginHelperv2 will result in an exception being
	 * thrown unless clearPermissions is called in between.
	 * <p/>
	 * This method is only meaningful if called before the session is open. If
	 * this is called after the session is opened, and the list of permissions
	 * passed in is not a subset of the permissions granted during the
	 * authorization, it will log an error.
	 * <p/>
	 * Since the session can be automatically opened when the LoginHelperv2 is
	 * constructed, it's important to always pass in a consistent set of
	 * permissions to this method, or manage the setting of permissions outside
	 * of the LoginHelperv2 class altogether (by managing the session
	 * explicitly).
	 * 
	 * @param permissions
	 *            the read permissions to use
	 * 
	 * @throws UnsupportedOperationException
	 *             if setPublishPermissions has been called
	 */
	public void setReadPermissions(List<String> permissions) {
		properties.setReadPermissions(permissions, session);
	}

	/**
	 * Set the permissions to use when the session is opened. The permissions
	 * here should only be publish permissions. If any read permissions are
	 * included, the login attempt by the user may fail. The LoginHelperv2 can
	 * only be associated with either read permissions or publish permissions,
	 * but not both. Calling both setReadPermissions and setPublishPermissions
	 * on the same instance of LoginHelperv2 will result in an exception being
	 * thrown unless clearPermissions is called in between.
	 * <p/>
	 * This method is only meaningful if called before the session is open. If
	 * this is called after the session is opened, and the list of permissions
	 * passed in is not a subset of the permissions granted during the
	 * authorization, it will log an error.
	 * <p/>
	 * Since the session can be automatically opened when the LoginHelperv2 is
	 * constructed, it's important to always pass in a consistent set of
	 * permissions to this method, or manage the setting of permissions outside
	 * of the LoginHelperv2 class altogether (by managing the session
	 * explicitly).
	 * 
	 * @param permissions
	 *            the read permissions to use
	 * 
	 * @throws UnsupportedOperationException
	 *             if setReadPermissions has been called
	 * @throws IllegalArgumentException
	 *             if permissions is null or empty
	 */
	public void setPublishPermissions(List<String> permissions) {
		properties.setPublishPermissions(permissions, session);
	}

	/**
	 * Clears the permissions currently associated with this LoginHelperv2.
	 */
	public void clearPermissions() {
		properties.clearPermissions();
	}

	/**
	 * Sets the login behavior for the session that will be opened. If null is
	 * specified, the default ({@link SessionLoginBehavior
	 * SessionLoginBehavior.SSO_WITH_FALLBACK} will be used.
	 * 
	 * @param loginBehavior
	 *            The {@link SessionLoginBehavior SessionLoginBehavior} that
	 *            specifies what behaviors should be attempted during
	 *            authorization.
	 */
	public void setLoginBehavior(SessionLoginBehavior loginBehavior) {
		properties.setLoginBehavior(loginBehavior);
	}

	/**
	 * Gets the login behavior for the session that will be opened. If null is
	 * returned, the default ({@link SessionLoginBehavior
	 * SessionLoginBehavior.SSO_WITH_FALLBACK} will be used.
	 * 
	 * @return loginBehavior The {@link SessionLoginBehavior
	 *         SessionLoginBehavior} that specifies what behaviors should be
	 *         attempted during authorization.
	 */
	public SessionLoginBehavior getLoginBehavior() {
		return properties.getLoginBehavior();
	}

	/**
	 * Gets the callback interface that will be called when the current user
	 * changes.
	 * 
	 * @return the callback interface
	 */
	public UserInfoChangedCallback getUserInfoChangedCallback() {
		return userInfoChangedCallback;
	}

	/**
	 * Sets the callback interface that will be called when the current user
	 * changes.
	 * 
	 * @param userInfoChangedCallback
	 *            the callback interface
	 */
	public void setUserInfoChangedCallback(
			UserInfoChangedCallback userInfoChangedCallback) {
		this.userInfoChangedCallback = userInfoChangedCallback;
	}

	/**
	 * Sets the callback interface that will be called whenever the status of
	 * the Session associated with this LoginHelperv2 changes. Note that updates
	 * will only be sent to the callback while the LoginHelperv2 is actually
	 * attached to a window.
	 * 
	 * @param callback
	 *            the callback interface
	 */
	public void setSessionStatusCallback(Session.StatusCallback callback) {
		properties.setSessionStatusCallback(callback);
	}

	/**
	 * Sets the callback interface that will be called whenever the status of
	 * the Session associated with this LoginHelperv2 changes.
	 * 
	 * @return the callback interface
	 */
	public Session.StatusCallback getSessionStatusCallback() {
		return properties.getSessionStatusCallback();
	}

	/**
	 * Provides an implementation for {@link Activity#onActivityResult
	 * onActivityResult} that updates the Session based on information returned
	 * during the authorization flow. The Activity containing this view should
	 * forward the resulting onActivityResult call here to update the Session
	 * state based on the contents of the resultCode and data.
	 * 
	 * @param requestCode
	 *            The requestCode parameter from the forwarded call. When this
	 *            onActivityResult occurs as part of Facebook authorization
	 *            flow, this value is the activityCode passed to open or
	 *            authorize.
	 * @param resultCode
	 *            An int containing the resultCode parameter from the forwarded
	 *            call.
	 * @param data
	 *            The Intent passed as the data parameter from the forwarded
	 *            call.
	 * @return A boolean indicating whether the requestCode matched a pending
	 *         authorization request for this Session.
	 * @see Session#onActivityResult(Activity, int, int, Intent)
	 */
	public boolean onActivityResult(int requestCode, int resultCode, Intent data) {

		if (session != null) {
			return session.onActivityResult((Activity) context, requestCode,
					resultCode, data);
		} else {
			return false;
		}
	}

	/**
	 * Set the Session object to use instead of the active Session. Since a
	 * Session cannot be reused, if the user logs out from this Session, and
	 * tries to log in again, a new Active Session will be used instead.
	 * <p/>
	 * If the passed in session is currently opened, this method will also
	 * attempt to load some user information for display (if needed).
	 * 
	 * @param newSession
	 *            the Session object to use
	 * @throws FacebookException
	 *             if errors occur during the loading of user information
	 */

	private void finishInit() {

		if (session != null && session.isOpened()) {
			fetchUserInfo();
		}

	}

	/**
	 * Sets the fragment that contains this control. This allows the
	 * LoginHelperv2 to be embedded inside a Fragment, and will allow the
	 * fragment to receive the
	 * {@link Fragment#onActivityResult(int, int, android.content.Intent)
	 * onActivityResult} call rather than the Activity.
	 * 
	 * @param fragment
	 *            the fragment that contains this control
	 */
	public void setFragment(Fragment fragment) {
		parentFragment = fragment;
	}

	// For testing purposes only
	List<String> getPermissions() {
		return properties.getPermissions();
	}

	void setProperties(LoginHelperv2Properties properties) {
		this.properties = properties;
	}

	private boolean initializeActiveSessionWithCachedToken(Context context) {
		if (context == null) {
			return false;
		}

		session = Session.getActiveSession();
		if (session != null) {
			return session.isOpened();
		}

		String applicationId = context.getString(R.string.app_face_id);
		if (applicationId == null) {
			return false;
		}
		session = new Session.Builder(context).setApplicationId(
				context.getString(R.string.app_face_id)).build();

		Session.setActiveSession(session);

		return Session.openActiveSessionFromCache(context) != null;
	}

	private void fetchUserInfo() {
		if (fetchUserInfo) {
			final Session currentSession = session;
			if (currentSession != null) {
				if (currentSession != userInfoSession) {
					Request request = Request.newMeRequest(currentSession,
							new Request.GraphUserCallback() {
								@Override
								public void onCompleted(GraphUser me,
										Response response) {
									if (currentSession == session) {
										user = me;
										if (userInfoChangedCallback != null) {
											userInfoChangedCallback
													.onUserInfoFetched(user);
										}
									}
									if (response.getError() != null) {
										handleError(response.getError()
												.getException());
									}
								}
							});
					Request.executeBatchAsync(request);
					userInfoSession = currentSession;
				}
			} else {
				user = null;
				if (userInfoChangedCallback != null) {
					userInfoChangedCallback.onUserInfoFetched(user);
				}
			}
		}
	}

	public void logout() {
		session.closeAndClearTokenInformation();
	}

	public void login() {
		Session currentSession = Session.getActiveSession();
		if (currentSession == null || currentSession.getState().isClosed()) {
			currentSession = new Session.Builder(context).setApplicationId(
					context.getString(R.string.app_face_id)).build();
			Session.setActiveSession(currentSession);
		}
		if (!currentSession.isOpened()) {
			Session.OpenRequest openRequest = null;
			if (parentFragment != null) {
				openRequest = new Session.OpenRequest(parentFragment);
			} else if (context instanceof Activity) {
				openRequest = new Session.OpenRequest((Activity) context);
			}

			if (openRequest != null) {
				openRequest.setDefaultAudience(properties.defaultAudience);
				openRequest.setPermissions(properties.permissions);
				openRequest.setLoginBehavior(properties.loginBehavior);
				openRequest.setCallback(properties.sessionStatusCallback);

				try {

					if (SessionAuthorizationType.PUBLISH
							.equals(properties.authorizationType)) {
						currentSession.openForPublish(openRequest);
					} else {
						currentSession.openForRead(openRequest);
					}
				} catch (Exception e) {
					// TODO: handle exception
					Log.e("login_helper", e.getMessage());
				}
			}
		}
	}

	public boolean isLogin() {

		Session session = Session.getActiveSession();
		return session != null && session.getState().isOpened();
	}

	// public void closeAndClearSession() {
	// Session session = Session.getActiveSession();
	//
	// }

	// private class LoginHelperv2Callback implements Session.StatusCallback {
	// @Override
	// public void call(Session session, SessionState state,
	// Exception exception) {
	// fetchUserInfo();
	// if (exception != null) {
	// handleError(exception);
	// }
	//
	// if (properties.sessionStatusCallback != null) {
	// properties.sessionStatusCallback
	// .call(session, state, exception);
	// }
	// }
	// };

	void handleError(Exception exception) {
		if (properties.onErrorListener != null) {
			if (exception instanceof FacebookException) {
				properties.onErrorListener
						.onError((FacebookException) exception);
			} else {
				properties.onErrorListener.onError(new FacebookException(
						exception));
			}
		}
	}

}
